home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/python
- # Pretend to be /usr/bin/id3v2 from id3lib, sort of.
- # Copyright 2005 Joe Wreschnig
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of version 2 of the GNU General Public License as
- # published by the Free Software Foundation.
-
- import os
- import sys
- import locale
-
- from optparse import OptionParser, SUPPRESS_HELP
-
- try:
- import mutagen, mutagen.id3
- except ImportError:
- # Run as ./mid3v2 out of tools/
- sys.path.append(os.path.abspath("../"))
- import mutagen, mutagen.id3
-
- VERSION = (1, 3)
-
- global verbose
- verbose = True
-
- class ID3OptionParser(OptionParser):
- def __init__(self):
- mutagen_version = ".".join(map(str, mutagen.version))
- my_version = ".".join(map(str, VERSION))
- version = "mid3v2 %s\nUses Mutagen %s" % (my_version, mutagen_version)
- self.edits = []
- OptionParser.__init__(
- self, version=version,
- usage="%prog [OPTION] [FILE]...",
- description="Mutagen-based replacement for id3lib's id3v2.")
-
- def format_help(self, *args, **kwargs):
- text = OptionParser.format_help(self, *args, **kwargs)
- return text + """\
- You can set the value for any ID3v2 frame by using '--' and then a frame ID.
- For example:
- mid3v2 --TIT3 "Monkey!" file.mp3
- would set the "Subtitle/Description" frame to "Monkey!".
-
- Any editing operation will cause the ID3 tag to be upgraded to ID3v2.4.
- """
-
- def list_frames(option, opt, value, parser):
- items = mutagen.id3.Frames.items()
- items.sort()
- for name, frame in items:
- print " --%s %s" % (name, frame.__doc__.split("\n")[0])
- raise SystemExit
-
- def list_frames_2_2(option, opt, value, parser):
- items = mutagen.id3.Frames_2_2.items()
- items.sort()
- for name, frame in items:
- print " --%s %s" % (name, frame.__doc__.split("\n")[0])
- raise SystemExit
-
- def list_genres(option, opt, value, parser):
- for i, genre in enumerate(mutagen.id3.TCON.GENRES):
- print "%3d: %s" % (i, genre)
- raise SystemExit
-
- def delete_tags(filenames, v1, v2):
- for filename in filenames:
- if verbose:
- print "deleting ID3 tag info in %s" % filename
- mutagen.id3.delete(filename, v1, v2)
-
- def delete_frames(deletes, filenames):
- frames = deletes.split(",")
- for filename in filenames:
- if verbose:
- print "deleting %s from %s" % (deletes, filename)
- try:
- id3 = mutagen.id3.ID3(filename)
- except mutagen.id3.ID3NoHeaderError:
- if verbose:
- print "No ID3 header found; skipping."
- except StandardError, err:
- print str(err)
- else:
- map(id3.delall, frames)
- id3.save()
-
- def write_files(edits, filenames):
- enc = locale.getpreferredencoding()
- edits = [(frame[2:], value.decode(enc)) for (frame, value) in edits]
-
- # preprocess:
- # for all [frame,value] pairs in the edits list
- # gather values for identical frames into a list
- tmp = {}
- for frame, value in edits:
- if tmp.has_key(frame):
- tmp[frame].append(value)
- else:
- tmp[frame] = [value]
- # edits is now a dictionary of frame -> [list of values]
- edits = tmp
-
- for filename in filenames:
- if verbose:
- print "Writing", filename
- try:
- id3 = mutagen.id3.ID3(filename)
- except mutagen.id3.ID3NoHeaderError:
- if verbose:
- print "No ID3 header found; creating a new tag"
- id3 = mutagen.id3.ID3()
- except StandardError, err:
- print str(err)
- continue
- for (frame, vlist) in edits.items():
- if frame == "COMM":
- for value in vlist:
- values = value.split(":")
- if len(values) == 1:
- value, desc, lang = values[0], "", "eng"
- elif len(values) == 2:
- desc, value, lang = values[0], values[1], "eng"
- else:
- value = ":".join(values[1:-1])
- desc, lang = values[0], values[-1]
- frame = mutagen.id3.COMM(
- encoding=3, text=value, lang=lang, desc=desc)
- elif issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame):
- frame = mutagen.id3.Frames[frame](encoding=3, url=vlist)
- else:
- frame = mutagen.id3.Frames[frame](encoding=3, text=vlist)
- id3.add(frame)
- id3.save(filename)
-
- def list_tags(filenames):
- enc = locale.getpreferredencoding()
- for filename in filenames:
- print "IDv2 tag info for %s:" % filename
- try:
- id3 = mutagen.id3.ID3(filename, translate=False)
- except StandardError, err:
- print str(err)
- else:
- print id3.pprint().encode(enc, "replace")
-
- def list_tags_raw(filenames):
- for filename in filenames:
- print "Raw IDv2 tag info for %s:" % filename
- try:
- id3 = mutagen.id3.ID3(filename, translate=False)
- except StandardError, err:
- print str(err)
- else:
- for frame in id3.values():
- print repr(frame)
-
- def main(argv):
- parser = ID3OptionParser()
- parser.add_option(
- "-v", "--verbose", action="store_true", dest="verbose", default=False,
- help="be verbose")
- parser.add_option(
- "-q", "--quiet", action="store_false", dest="verbose",
- help="be quiet (the default)")
- parser.add_option(
- "-f", "--list-frames", action="callback", callback=list_frames,
- help="Display all possible frames for ID3v2.3 / ID3v2.4")
- parser.add_option(
- "--list-frames-v2.2", action="callback", callback=list_frames_2_2,
- help="Display all possible frames for ID3v2.2")
- parser.add_option(
- "-L", "--list-genres", action="callback", callback=list_genres,
- help="Lists all ID3v1 genres")
- parser.add_option(
- "-l", "--list", action="store_const", dest="action", const="list",
- help="Lists the tag(s) on the file(s)")
- parser.add_option(
- "--list-raw", action="store_const", dest="action", const="list-raw",
- help="Lists the tag(s) on the file(s) in Python format")
- parser.add_option(
- "-d", "--delete-v2", action="store_const", dest="action",
- const="delete-v2", help="Deletes ID3v2 tags")
- parser.add_option(
- "-s", "--delete-v1", action="store_const", dest="action",
- const="delete-v1", help="Deletes ID3v1 tags")
- parser.add_option(
- "-D", "--delete-all", action="store_const", dest="action",
- const="delete-v1-v2", help="Deletes ID3v1 and ID3v2 tags")
- parser.add_option(
- '--delete-frames', metavar='FID1,FID2,...', action='store',
- dest='deletes', default='', help="Delete the given frames")
- parser.add_option(
- "-C", "--convert", action="store_const", dest="action",
- const="convert",
- help="Convert tags to ID3v2.4 (any editing will do this)")
-
- parser.add_option(
- "-a", "--artist", metavar='"ARTIST"', action="callback",
- help="Set the artist information", type="string",
- callback=lambda *args: args[3].edits.append(("--TPE1", args[2])))
- parser.add_option(
- "-A", "--album", metavar='"ALBUM"', action="callback",
- help="Set the album title information", type="string",
- callback=lambda *args: args[3].edits.append(("--TALB", args[2])))
- parser.add_option(
- "-t", "--song", metavar='"SONG"', action="callback",
- help="Set the song title information", type="string",
- callback=lambda *args: args[3].edits.append(("--TIT2", args[2])))
- parser.add_option(
- "-c", "--comment", metavar='"DESCRIPTION":"COMMENT":"LANGUAGE"',
- action="callback", help="Set the comment information", type="string",
- callback=lambda *args: args[3].edits.append(("--COMM", args[2])))
- parser.add_option(
- "-g", "--genre", metavar='"GENRE"', action="callback",
- help="Set the genre or genre number", type="string",
- callback=lambda *args: args[3].edits.append(("--TCON", args[2])))
- parser.add_option(
- "-y", "--year", "--date", metavar='YYYY[-MM-DD]', action="callback",
- help="Set the year/date", type="string",
- callback=lambda *args: args[3].edits.append(("--TDRC", args[2])))
- parser.add_option(
- "-T", "--track", metavar='"num/num"', action="callback",
- help="Set the track number/(optional) total tracks", type="string",
- callback=lambda *args: args[3].edits.append(("--TRCK", args[2])))
-
- for frame in mutagen.id3.Frames:
- if issubclass(mutagen.id3.Frames[frame], mutagen.id3.TextFrame) \
- or issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame):
- parser.add_option(
- "--" + frame, action="callback", help=SUPPRESS_HELP,
- type='string', metavar="value", # optparse blows up with this
- callback=lambda *args: args[3].edits.append(args[1:3]))
-
- (options, args) = parser.parse_args(argv[1:])
- global verbose
- verbose = options.verbose
-
- if args:
- if parser.edits or options.deletes:
- if options.deletes:
- delete_frames(options.deletes, args)
- if parser.edits:
- write_files(parser.edits, args)
- elif options.action in [None, 'list']:
- list_tags(args)
- elif options.action == "list-raw":
- list_tags_raw(args)
- elif options.action == "convert":
- write_files([], args)
- elif options.action.startswith("delete"):
- delete_tags(args, "v1" in options.action, "v2" in options.action)
- else:
- parser.print_help()
- else:
- parser.print_help()
-
- if __name__ == "__main__":
- main(sys.argv)
-